package com.tyros.common.secure;

import com.tyros.common.secure.codec.Codec;
import com.tyros.common.secure.password.BcryptPasswordEncoder;
import com.tyros.common.secure.password.PBKDF2PasswordEncoder;
import com.tyros.common.secure.password.PasswordEncoder;
import com.tyros.common.secure.symmetrics.SymmetricKit;
import com.tyros.common.secure.utils.RandomKit;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * 加密控制台
 *
 * @author zsy
 */
public class CryptBoostrap {

    private static final String DEFAULT_CMD_NAME = "help";

    private static final String ARGUMENT_SEPARATOR = ",";

    private static final String ARGUMENTS_SEPARATOR = "=";

    private final static String OUTPUT = "Usage: java -cp common-crypto-1.0.0.jar " + CryptBoostrap.class.getName() + " ";

    /**
     * 参数解析
     *
     * @param args 参数
     * @return 解析后的参数
     */
    private static Map<String, String> argsParse(String args) {
        if (args == null || args.trim().isEmpty()) {
            return new HashMap<>(1);
        }
        AtomicInteger index = new AtomicInteger();
        Map<String, String> map = new LinkedHashMap<>(8);
        Stream.of(args.split(ARGUMENT_SEPARATOR)).forEach(s -> {
            int equalIndex = s.indexOf(ARGUMENTS_SEPARATOR);
            if (equalIndex > 0 && equalIndex + 1 < s.length()) {
                String name = s.substring(0, equalIndex);
                String value = s.substring(equalIndex + 1);
                if (!value.trim().isEmpty()) {
                    map.put(name, value);
                }
            } else {
                map.put(index.getAndIncrement() + "", s);
            }
        });
        return map;

    }

    public static void main(String[] args) {
        String cmdName = DEFAULT_CMD_NAME;
        if (args.length >= 1) {
            cmdName = args[0];
        }
        StringBuffer sb = new StringBuffer();
        CMD cmd;
        if (DEFAULT_CMD_NAME.equalsIgnoreCase(cmdName) || (cmd = CMD.of(cmdName)) == null) {
            sb.append(OUTPUT).append("[cmdName] [arguments]\n");
            sb.append("For example: \n");
            for (CMD cmd2 : CMD.values()) {
                sb.append(OUTPUT).append(cmd2.getExample()).append("\n");
            }
            System.out.println(sb);
        } else {
            String arguments = String.join(" ", args);
            arguments = arguments.substring(cmdName.length()).trim();
            boolean test = cmd.getPredicate().test(arguments);
            if (!test) {
                System.err.println(OUTPUT + cmd.getExample());
            } else {
                cmd.getConsumer().accept(arguments);
            }
        }
    }

    public enum CMD {
        /**
         * 根据主密钥生成工作密钥
         */
        DUMP_WORK_KEY("dump_work_key", args -> {
            Map<String, String> parameters = argsParse(args);
            String password = parameters.get("password");
            String salt = parameters.get("salt");
            byte[] saltBytes = salt != null ? Codec.BASE64.decode(salt) : RandomKit.random(16);
            PasswordEncoder passwordEncoder = new PBKDF2PasswordEncoder();
            try {
                String key = passwordEncoder.encode(password, saltBytes);
                String sb = "=====================Console output========================\n" +
                        "cmd      :dump_work_key" + "\n" +
                        "password :" + password + "\n" +
                        "key      :" + key.split("\\$")[2];
                System.out.println(sb);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, args -> {
            Map<String, String> parameters = argsParse(args);
            return parameters.containsKey("password");
        }, "dump_work_key password=input master password"),

        /**
         * 使用bcrypt加密密码
         */
        PASSWORD_ENCODER("password_encoder", args -> {
            Map<String, String> parameters = argsParse(args);
            String password = parameters.get("password");
            PasswordEncoder passwordEncoder = new BcryptPasswordEncoder();
            try {
                String key = passwordEncoder.encode(password, null);
                String sb = "=====================Console output========================\n" +
                        "cmd      :password_encoder" + "\n" +
                        "password :" + password + "\n" +
                        "key      :" + key;
                System.out.println(sb);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, args -> {
            Map<String, String> parameters = argsParse(args);
            return parameters.containsKey("password");
        }, "password_encoder password=input raw password"),

        /**
         * 使用bcrypt加密密码
         */
        PASSWORD_MATCHER("password_matcher", args -> {
            Map<String, String> parameters = argsParse(args);
            String password = parameters.get("password");
            String encodedPassword = parameters.get("encodedPassword");
            PasswordEncoder passwordEncoder = new BcryptPasswordEncoder();
            try {
                boolean matches = passwordEncoder.matches(password, encodedPassword);
                String sb = "=====================Console output========================\n" +
                        "cmd      :password_matcher" + "\n" +
                        "matches  :" + matches;
                System.out.println(sb);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, args -> {
            Map<String, String> parameters = argsParse(args);
            return parameters.containsKey("password");
        }, "password_matcher password=input raw password,encodedPassword=input encoded password"),

        /**
         * AES加密
         */
        AES_GCM_ENCRYPT("aes_gcm_encrypt", args -> {
            Map<String, String> parameters = argsParse(args);
            String password = parameters.get("password");
            String data = parameters.get("data");
            try {
                String encrypted = SymmetricKit.encrypt(data, password);
                String sb = "=====================Console output========================\n" +
                        "cmd            :aes_gcm_encrypt" + "\n" +
                        "password       :" + password + "\n" +
                        "raw data       :" + data + "\n" +
                        "encrypted data :" + encrypted;
                System.out.println(sb);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, args -> {
            Map<String, String> parameters = argsParse(args);
            return parameters.containsKey("password") && parameters.containsKey("data");
        }, "aes_gcm_encrypt password=RpqmQjackH47zasrsxfMSs9cbjlQjfQpqGjM+EcEFzE=,data=input raw data"),

        /**
         * AES解密
         */
        AES_GCM_DECRYPT("aes_gcm_decrypt", args -> {
            Map<String, String> parameters = argsParse(args);
            String password = parameters.get("password");
            String data = parameters.get("data");
            try {
                String encrypted = SymmetricKit.decrypt(data, password);
                String sb = "=====================Console output========================\n" +
                        "cmd            :aes_gcm_decrypt" + "\n" +
                        "password       :" + password + "\n" +
                        "encrypted data :" + data + "\n" +
                        "raw data       :" + encrypted;
                System.out.println(sb);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, args -> {
            Map<String, String> parameters = argsParse(args);
            return parameters.containsKey("password") && parameters.containsKey("data");
        }, "aes_gcm_decrypt password=RpqmQjackH47zasrsxfMSs9cbjlQjfQpqGjM+EcEFzE=,data=KrRnVOfLo3nS8RjpkgUa5tYLx09X30ru9vcB9cnv9JV7ueqzKpILHm9y2ex0whGUZbexPrgZPkGpMVwV");

        private final String cmdName;

        private final Consumer<String> consumer;

        private final Predicate<String> predicate;

        private final String example;

        CMD(String cmdName, Consumer<String> consumer, Predicate<String> predicate, String example) {
            this.cmdName = cmdName;
            this.consumer = consumer;
            this.predicate = predicate;
            this.example = example;
        }

        public String getCmdName() {
            return cmdName;
        }

        public Consumer<String> getConsumer() {
            return consumer;
        }

        public Predicate<String> getPredicate() {
            return predicate;
        }

        public String getExample() {
            return example;
        }

        public static CMD of(String cmdName) {
            return Arrays.stream(CMD.values()).filter(cmd -> cmd.getCmdName().equalsIgnoreCase(cmdName)).findFirst()
                    .orElse(null);
        }
    }
}
